package cn.st4rlight.util.json;

import static com.fasterxml.jackson.databind.type.TypeFactory.defaultInstance;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;

import javax.annotation.Nullable;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import com.fasterxml.jackson.core.JsonFactory.Feature;
import com.fasterxml.jackson.core.JsonFactoryBuilder;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import com.hubspot.jackson.datatype.protobuf.ProtobufModule;

import cn.st4rlight.util.exception.ServiceException;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/**
 * @author st4rlight <st4rlight@163.com>
 * Created on 2024-04-15
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ObjectMapperUtil {

    public static final String       EMPTY_JSON = "{}";
    public static final String EMPTY_ARRAY_JSON = "[]";

    /**
     * 关闭INTERN_FIELD_NAMES用于解决GC压力大，内存泄露问题
     * NOTE: 如果外部需要直接使用这个MAPPER，公司的做法是实现一个delegate，这个delegate的操作会转移到这个MAPPER中，对外提供的是delegate
     * 但是只允许转发一些普通方法，对于会修改MAPPER配置的方法一律抛出UnsupportedOperationException
     */
    private static final ObjectMapper MAPPER =
            new ObjectMapper(new JsonFactoryBuilder().configure(Feature.INTERN_FIELD_NAMES, false).build())
                    .registerModule(new GuavaModule())
                    .registerModule(new JavaTimeModule())
                    .registerModule(new ParameterNamesModule())
                    .registerModule(new ProtobufModule());

    static {
        // 允许未知属性
        MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        // 允许不加引号的key
        MAPPER.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
        // 允许注释
        MAPPER.enable(JsonParser.Feature.ALLOW_COMMENTS);
    }



    public static String toJson(@Nullable Object obj) {
        if (Objects.isNull(obj)) {
            return null;
        }

        try {
            return MAPPER.writeValueAsString(obj);
        } catch (JsonProcessingException ex) {
            throw ServiceException.of(ex);
        }
    }

    public static <T> T fromJson(@Nullable String json, Class<T> valueType) {
        if (StringUtils.isBlank(json)) {
            return null;
        }

        try {
            return MAPPER.readValue(json, valueType);
        } catch (JsonProcessingException ex) {
            throw ServiceException.of(ex);
        }
    }

    public static <T> T fromJson(@Nullable byte[] bytes, Class<T> valueType) {
        if (ArrayUtils.isEmpty(bytes)) {
            return null;
        }

        try {
            return MAPPER.readValue(bytes, valueType);
        } catch (IOException ex) {
            throw ServiceException.of(ex);
        }
    }

    public static <T> T fromJson(Object value, Class<T> valueType) {
        if (Objects.isNull(value)) {
            return null;
        } else if (value instanceof String) {
            return fromJson((String) value, valueType);
        } else if (value instanceof byte[]) {
            return fromJson((byte[]) value, valueType);
        } else {
            return null;
        }
    }

    public static <E, T extends Collection<E>> T fromJson(
        String json, Class<? extends Collection<?>> collectionType, Class<E> elementType
    ) {
        if (StringUtils.isBlank(json)) {
            json = EMPTY_ARRAY_JSON;
        }

        try {
            return MAPPER.readValue(json,
                    defaultInstance().constructCollectionType(collectionType, elementType));
        } catch (IOException ex) {
            throw ServiceException.of(ex);
        }
    }

    public static <E, T extends Collection<E>> T fromJson(
        byte[] bytes, Class<? extends Collection<?>> collectionType, Class<E> elementType
    ) {
        if (ArrayUtils.isEmpty(bytes)) {
            bytes = EMPTY_ARRAY_JSON.getBytes();
        }

        try {
            return MAPPER.readValue(bytes,
                    defaultInstance().constructCollectionType(collectionType, elementType));
        } catch (IOException ex) {
            throw ServiceException.of(ex);
        }
    }

    public static <K, V, T extends Map<K, V>> T fromJson(
        String json, Class<? extends Map> mapType, Class<K> keyType, Class<V> valueType
    ) {
        if (StringUtils.isBlank(json)) {
            json = EMPTY_JSON;
        }

        try {
            return MAPPER.readValue(json,
                    defaultInstance().constructMapType(mapType, keyType, valueType));
        } catch (IOException ex) {
            throw ServiceException.of(ex);
        }
    }

    public static <K, V, T extends Map<K, V>> T fromJson(
        byte[] bytes, Class<? extends Map> mapType, Class<K> keyType, Class<V> valueType
    ) {
        if (ArrayUtils.isEmpty(bytes)) {
            bytes = EMPTY_ARRAY_JSON.getBytes();
        }

        try {
            return MAPPER.readValue(bytes,
                    defaultInstance().constructMapType(mapType, keyType, valueType));
        } catch (IOException ex) {
            throw ServiceException.of(ex);
        }
    }

    public static Map<String, Object> fromJson(String json) {
        return fromJson(json, Map.class, String.class, Object.class);
    }

    public static Map<String, Object> fromJson(byte[] bytes) {
        return fromJson(bytes, Map.class, String.class, Object.class);
    }

    public static ObjectMapper mapper() {
        return MAPPER;
    }
}
